package fgin

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"reflect"
	"sync"

	"gitee.com/wu-jin-feng/fgin/fgin_plugs/fotel/ftracer"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/opentracing/opentracing-go"
	"google.golang.org/grpc/metadata"
)

// 获取返回值值
func GetHttpReturnBody(ctx *gin.Context) string {
	responseBody := ""
	if customWriter, ok := ctx.Get(FGINRESPONSE); ok {
		writer := customWriter.(*FginResponseWriter)
		// 获取响应状态码和响应体内容
		responseBody = writer.body.String()
	}
	return responseBody
}

// 返回数据
func ReturnJson(ctx *gin.Context, code int, data any, err error) {
	msg := ""
	switch code {
	case 200:
		msg = "success"
	default:
		if err == nil {
			err = errors.New("请求错误")
		}
		msg = err.Error()
	}
	out := map[string]any{
		"code": code,
		"msg":  msg,
		"data": data,
	}
	// trace不为空，返回给前端
	traceId := ftracer.GetTraceIdFromContext(ctx.Request.Context())
	if traceId != "" {
		out["trace"] = traceId
	}
	headerCode := code
	if headerCode > 599 || headerCode < 100 {
		logSuger.Info(fmt.Sprintf("%v,返回使用自定义code为%v,header_code切为400", ctx.Request.URL, headerCode), ctx)
		headerCode = 400
	}
	// 根据上下文传入，控制只返回一次给前端
	returnLockIntface, ok := ctx.Get("return_lock")
	if !ok {
		ctx.JSONP(headerCode, out)
		return
	}
	returnLock, ok := returnLockIntface.(chan int)
	if !ok {
		ctx.JSONP(headerCode, out)
		return
	}
	select {
	case <-returnLock:
		ctx.JSONP(headerCode, out)
	default:
		return
	}
}

// 查看数组内是否有
func ArrayFindString(search []string, flag string) bool {
	for _, v := range search {
		if v == flag {
			return true
		}
	}
	return false
}

// 生成新context，自动植入grpc的信息
func GetGrpcCtx(ctx context.Context) context.Context {
	span := opentracing.SpanFromContext(ctx)
	md := metadata.New(map[string]string{})
	if (fginConfig.Jaeger != Jaeger{}) {
		opentracing.GlobalTracer().Inject(
			span.Context(),
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(md),
		)
		// grpc传meta，不能用大写
		// span部分
		md = alertMd(md, "Uber-Trace-Id", "uber-trace-id")
	}

	c := metadata.NewOutgoingContext(ctx, md)
	return c
}

// 将context 转为header，实现span在http协议中传送
func GetCtxToHeader(ctx context.Context) http.Header {
	span := opentracing.SpanFromContext(ctx)
	header := http.Header{}
	if (fginConfig.Jaeger != Jaeger{}) {
		opentracing.GlobalTracer().Inject(
			span.Context(),
			opentracing.HTTPHeaders,
			opentracing.HTTPHeadersCarrier(header),
		)
	}
	return header
}

func alertMd(md metadata.MD, flag1, flag2 string) metadata.MD {
	if value, ok := md[flag1]; ok {
		var mu sync.Mutex
		mu.Lock()
		delete(md, flag1)
		md[flag2] = value
		mu.Unlock()
	}
	return md
}

// 往下查找msg
func findTagMsg(getObj reflect.Type, keyName string, msg *string) {
	switch getObj.Kind() {
	case reflect.Ptr:
		// 指针类型
		getObj = getObj.Elem()
	case reflect.Slice:
		// 切片类型
		getObj = getObj.Elem()
	default:
		_ = 1
	}
	// 非结构体类型，直接返回
	if getObj.Kind() != reflect.Struct {
		return
	}
	f, exist := getObj.FieldByName(keyName)
	if exist {
		*msg = f.Tag.Get("msg")
		return
	}
	// 没有找到继续，向下查找
	for i := 0; i < getObj.NumField(); i++ {
		findTagMsg(getObj.Field(i).Type, keyName, msg)
	}
}

// 绑定参数，利用的gin的bind逻辑，可返回自定义错误
// 优化 支持嵌套结构体往下查找
func FbindParams(ctx *gin.Context, input interface{}) error {
	if err := ctx.ShouldBind(input); err != nil {
		// 通过反射返回错误
		errMsg := err.Error()
		getObj := reflect.TypeOf(input)
		if errs, ok := err.(validator.ValidationErrors); ok {
			for _, e := range errs {
				msg := ""
				findTagMsg(getObj, e.Field(), &msg)
				if msg != "" {
					errMsg = msg
					break
				}
			}
		}
		return errors.New(errMsg)
	}
	return nil
}

// 绑定参数，使用validator的逻辑，可返回自定义参数名
func ValidateBindParams(ctx *gin.Context, input any) error {
	if err := ctx.ShouldBind(input); err != nil {
		return err
	}
	if err := ValidateStruct(input); err != nil {
		return err
	}
	return nil
}

// 查看目录是否存在
func FileOrDirExists(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		return os.IsExist(err)
	}
	return true
}

// 创建目录
func MakeDirAll(path string) error {
	if FileOrDirExists(path) {
		return nil
	}
	return os.MkdirAll(path, os.ModePerm)
}

// 指定数组元素删除,step表示要删除的位置，-1表示末尾
func DelArrItemStr(arr []string, step int) []string {
	endStep, arrLen := step, len(arr)
	if step < 0 {
		endStep = arrLen + step
	}
	arr = append(arr[:endStep], arr[endStep+1:]...)
	return arr
}

// 链路追踪主动上报
//
// 示例 JaegerSpanKV("追逐标识", ctx, false, k1, v1, k2, v2)
func JaegerSpanKV(spanName string, ctx context.Context, isErr bool, alternatingKeyValues ...any) {
	// 断言context
	if v, ok := ctx.(*gin.Context); ok {
		// gin 类型的context需独立提取
		ctx = v.Request.Context()
	}
	// 获取span
	span := opentracing.SpanFromContext(ctx)
	if span == nil {
		logSuger.Warn("上下文中不存在jaeger span, 停止链路上报", ctx)
		return
	}
	span = opentracing.StartSpan(spanName, opentracing.ChildOf(span.Context()))
	if isErr {
		span.SetTag("error", true)
	}
	span.LogKV(alternatingKeyValues...)
	span.Finish()
}

// 获取链路追踪的span
func JaegerSpan(spanName string, ctx context.Context) opentracing.Span {
	// 断言context
	if v, ok := ctx.(*gin.Context); ok {
		// gin 类型的context需独立提取
		ctx = v.Request.Context()
	}
	// 获取span
	span := opentracing.SpanFromContext(ctx)
	if span == nil {
		return nil
	}
	span = opentracing.StartSpan(spanName, opentracing.ChildOf(span.Context()))
	return span
}
